﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using PSE.AuditLogging.StorageService.Configuration;
using System.Diagnostics;
using PSE.Framework.Threading;
using System.IO;
using System.Configuration;
using PSE.Framework.ErrorLogging;

namespace PSE.AuditLogging.StorageService
{
    internal class ServiceExecutor : IDisposable
    {
        private static object _syncRoot = new object();
        private static object _syncRootProduct = new object();
        private static object _syncRootInstance = new object();

        private static ServiceExecutor _instance;

        private int _currentProcessPID;
        public bool IsRunning
        {
            get;
            private set;
        }

        //armazenar as instancias dos produtos e os processos das instancias
        private Dictionary<string, Dictionary<string, Process>> _productsxInstancesxProcesses;
        //armazenar os objetos de controle de execucao dos processos
        private Dictionary<string, Dictionary<string, CrossProcessManualResetEvent>> _productsxInstancesxProcessesControl;

        internal static ServiceExecutor GetInstance()
        {
            lock (_syncRootInstance)
            {
                if (_instance == null)
                    _instance = new ServiceExecutor();

                return _instance;
            }
        }

        #region Metodos de Instancia
        private ServiceExecutor()
        {
            _currentProcessPID = Process.GetCurrentProcess().Id;
        }

        internal void Start()
        {
            lock (_syncRoot)
            {
                if (!IsRunning)
                {
                    _productsxInstancesxProcesses = new Dictionary<string, Dictionary<string, Process>>();
                    _productsxInstancesxProcessesControl = new Dictionary<string, Dictionary<string, CrossProcessManualResetEvent>>();

                    IsRunning = true;

                    //recuperar a lista de produtos 
                    foreach (ProductConfigurationElement product in ConfigurationReader.LogServiceConfiguration.Products)
                    {
                        //criar o objeto de controle de execucao para o produto
                        _productsxInstancesxProcessesControl.Add(product.ProductId, new Dictionary<string, CrossProcessManualResetEvent>());
                        _productsxInstancesxProcesses.Add(product.ProductId, new Dictionary<string, Process>());

                        //varrer as instancias configuradas para o produto
                        foreach (InstanceConfigurationElement instance in product.Instances)
                        {
                            _productsxInstancesxProcessesControl[product.ProductId].Add(instance.InstanceId, new CrossProcessManualResetEvent(false, "PSE.AuditLogging.StorageService.MREExecutionControl_" + product.ProductId + "_" + instance.InstanceId));
                            _productsxInstancesxProcesses[product.ProductId].Add(instance.InstanceId, null);
                        }

                        try
                        {
                            //iniciar o produto
                            StartProduct(product.ProductId);
                        }
                        catch (Exception ex)
                        {
                            //apenas logar o erro e passar para o proximo produto
                            ErrorLogger.WriteLog(LogType.Error, ex);
                        }
                    }
                }
            }

        }

        internal void Stop()
        {
            lock (_syncRoot)
            {
                if (IsRunning)
                {
                    foreach (ProductConfigurationElement p in ConfigurationReader.LogServiceConfiguration.Products)
                    {
                        StopProduct(p.ProductId);
                    }
                    _productsxInstancesxProcesses = null;
                    _productsxInstancesxProcessesControl = null;

                    IsRunning = false;
                }
            }
        }

        internal bool StartProduct(string productId)
        {
            bool started = true;

            lock (_syncRootProduct)
            {
                ValidateProduct(productId);

                ProductConfigurationElement product = ConfigurationReader.LogServiceConfiguration.Products[productId];

                foreach (InstanceConfigurationElement instance in product.Instances)
                {
                    try
                    {
                        //se n conseguir iniciar alguma instancia o produto nao estara iniciado
                        if (!StartInstance(productId, instance.InstanceId))
                            started = false;
                    }
                    catch (Exception ex)
                    {
                        //apenas logar o erro e passar para o proximo
                        ErrorLogger.WriteLog(LogType.Error, ex);
                    }
                }
            }

            return started;
        }

        internal bool StartInstance(string productId, string instanceId)
        {
            bool started = false;

            lock (_syncRootInstance)
            {
                if (IsRunning)
                {
                    ValidateInstance(productId, instanceId);

                    //somente iniciar o processo se ainda nao estiver rodando
                    if (_productsxInstancesxProcesses[productId][instanceId] == null || _productsxInstancesxProcesses[productId][instanceId].HasExited)
                    {
                        _productsxInstancesxProcessesControl[productId][instanceId].Reset();

                        string exeDir = Path.GetDirectoryName(System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);

                        //criar um processo
                        Process proc = new Process();
                        proc.StartInfo.Arguments = "\"" + productId + "\" \"" + instanceId + "\" " + _currentProcessPID.ToString();
                        proc.StartInfo.RedirectStandardError = true;
                        proc.StartInfo.RedirectStandardOutput = true;
                        proc.StartInfo.FileName = Path.Combine(exeDir, "PSE.AuditLogging.StorageService.Instance.exe");
                        proc.StartInfo.CreateNoWindow = true;
                        proc.StartInfo.UseShellExecute = false;
                        proc.Start();

                        //objecto utilizado para aguardar um sinal do processo criado indicando que foi iniciado com sucesso
                        CrossProcessManualResetEvent processStartWait = new CrossProcessManualResetEvent(false, "PSE.AuditLogging.StorageService.MRENotifyParent_" + productId + "_" + instanceId);
                        //se conseguiu iniciar o processo com sucesso
                        if (processStartWait.WaitOne(60000))
                        {
                            _productsxInstancesxProcesses[productId][instanceId] = proc;
                            started = true;
                        }
                        else
                            throw new Exception(string.Format("Unable to start the product '{0}' Audit Logging Service instance. See system log for more details.", productId));
                    }
                    else//ja esta em execucao
                        started = true;
                }
                else
                {
                    throw new InvalidOperationException("The BM&FBOVESPA Audit Logging Storage Service it's not started. Please call 'Start' method from the service before try to start a product.");
                }
            }

            return started;
        }

        internal bool StopProduct(string productId)
        {
            bool stoped = true;

            lock (_syncRootProduct)
            {
                ValidateProduct(productId);

                ProductConfigurationElement product = ConfigurationReader.LogServiceConfiguration.Products[productId];

                foreach (InstanceConfigurationElement instance in product.Instances)
                {
                    try
                    {
                        //se n conseguir finalizar alguma instancia, o produto nao estara finalizado
                        if (!StopInstance(productId, instance.InstanceId))
                            stoped = false;
                    }
                    catch (Exception ex)
                    {
                        //apenas logar o erro e passar para o proximo
                        ErrorLogger.WriteLog(LogType.Error, ex);
                    }
                }
            }

            return stoped;
        }

        internal bool StopInstance(string productId, string instanceId)
        {
            bool stoped = false;

            lock (_syncRootInstance)
            {
                ValidateInstance(productId, instanceId);

                if (IsRunning)
                {
                    //se existe uma entrada no dicionario de controladores de execucao
                    if (_productsxInstancesxProcessesControl[productId].ContainsKey(instanceId))
                    {
                        //enviar evento para finalizar o processo
                        _productsxInstancesxProcessesControl[productId][instanceId].Set();

                        //se existe uma entrada no dicionario de processos em execucao
                        if (_productsxInstancesxProcesses[productId].ContainsKey(instanceId))
                        {
                            Process proc = _productsxInstancesxProcesses[productId][instanceId];

                            if (proc != null)
                            {
                                for (int i = 0; i < 10; i++)
                                {
                                    proc.Refresh();
                                    proc.WaitForExit(1000);//aguardar o termino do processo
                                    if (proc.HasExited)
                                        break;
                                }

                                //se o processo n morrer por vias normais, forcar a morte
                                if (!proc.HasExited)
                                    proc.Kill();

                                stoped = true;
                            }
                        }
                    }
                }
                else
                {
                    throw new InvalidOperationException("The BM&FBOVESPA Audit Logging Storage Service it's not started. Please call 'Start' method from the service before try to stop a product.");
                }
            }

            return stoped;
        }

        private void ValidateProduct(string productId)
        {
            if (!_productsxInstancesxProcessesControl.ContainsKey(productId) || !_productsxInstancesxProcesses.ContainsKey(productId))
                throw new ConfigurationErrorsException(string.Format("The product {0} it's not configured in the BM&FBOVESPA Audit Logging Storage Service config file.", productId));
        }

        private void ValidateInstance(string productId, string instanceId)
        {
            ValidateProduct(productId);
            if (!_productsxInstancesxProcessesControl[productId].ContainsKey(instanceId) || !_productsxInstancesxProcesses[productId].ContainsKey(instanceId))
                throw new ConfigurationErrorsException(string.Format("The instance {0} it's not configured from the product {1} in the BM&FBOVESPA Audit Logging Storage Service config file.", instanceId, productId));

        }

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        #endregion
        #endregion

        internal bool RestartProduct(string productId)
        {
            bool restarted = true;

            if (!this.StopProduct(productId))
                restarted = false;

            if (!this.StartProduct(productId))
                restarted = false;

            return restarted;
        }

        internal List<string> GetRunningProducts()
        {
            List<string> ret = new List<string>();
            bool isRunning;
            foreach (KeyValuePair<string, Dictionary<string, Process>> item in this._productsxInstancesxProcesses)
            {
                isRunning = false;
                //se ao menos uma instacia esta sendo executada para o produto
                foreach (KeyValuePair<string, Process> item1 in item.Value)
                {
                    if (!item1.Value.HasExited)
                    {
                        isRunning = true;
                        break;
                    }
                }

                if (isRunning)
                    ret.Add(item.Key);
            }

            return ret;
        }

        internal List<string> GetRunningInstances(string productId)
        {
            List<string> ret = new List<string>();
            foreach (KeyValuePair<string, Dictionary<string, Process>> item in this._productsxInstancesxProcesses)
            {
                //verificar as instancias em execucao
                foreach (KeyValuePair<string, Process> item1 in item.Value)
                {
                    if (!item1.Value.HasExited)
                        ret.Add(item.Key);
                }
            }

            return ret;
        }
    }
}
